/***
    Amrita ITEWS - Copyright (C) 2009 Amrita Vishwa Vidyapeetham, Amritapuri.
                                       (http://www.amrita.edu)
    ***************************************************************************
    This file is part of the Amrita ITEWS distribution.
    Amrita ITEWS is free software; you can redistribute it and/or modify it
    under the terms of the GNU General Public License (version 2) as published
    by the Free Software Foundation AND MODIFIED BY the Amrita ITEWS exception.
    ***NOTE*** The exception to the GPL is included to allow you to distribute
    a combined work that includes Amrita ITEWS without being obliged to provide
    the source code for proprietary components outside of the Amrita ITEWS
    software. Amrita ITEWS is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
    Public License for more details. You should have received a copy of the GNU
    General Public License and the Amrita ITEWS license exception along with
    Amrita ITEWS if not then it can be viewed here:
    http://itews.amrita.ac.in/license.html.


    Documentation, latest information, license and contact details are at:
    http://itews.amrita.ac.in/


    Amrita ITEWS source code can be found at:
    http://code.google.com/p/itews


    The initial code-base of Amrita ITEWS was developed by Amrita Vishwa
    Vidyapeetham as part of the project titled,"Intelligent & Interactive
    Telematics using Emerging Wireless Technologies for Transport Systems
    (ITEWS)" sponsored by Technology Information Forecasting and Assessment
    Council (TIFAC), India.
***/

/*! \file str_printf.c
    \brief String Print Library Source file.
*/

#include <str_printf.h>

static int8* digits = "0123456789ABCDEF";

static int32 write_str(char *ptr, int32 sz, char *p);
static int32 write_number_ULONG(char *ptr, int32 sz, uint32 number, uint32 flags);
static int32  write_number_ULLONG(char *ptr, int32 sz, unsigned long long number, uint32 flags);
static int32 write_number_UINT(char *ptr, int32 sz, uint32 number, uint32 flags);
static int32 write_number_FLOAT(char *ptr, int32 sz, float number, uint32 flags);
static int32  write_number_DOUBLE(char *ptr, int32 sz, double number, uint32 flags);
static int32 write_number_LDOUBLE(char *ptr, int32 sz, long double number, uint32 flags);

int32 str_vsnprintf( char *buf, size_t sz, const char* format, va_list args )
{
    int32 count = 0;
    int32 len = 0;
//uint32 flags;
    uint32 c;

    while(( (c = *(format++)) != '\0')&& (sz > 0) )
    {
        if(c != '%')
        {
            *buf++ = c;
            count++;
            sz--;
            continue;
        }else
        if((c = *(format++)) != '\0')
        {
            void* t;
            uint32 flags = 0;
            switch(c)
            {
                case '%':
                    *buf++ = c;
                    count++;
                    sz--;
                    continue;
                case 'c':
                    c = (int8)va_arg(args, int32);
                    *buf++ = c;
                    sz--;
                    count++;
                    continue;
                case 's':
                    t = (void*)va_arg(args, int8*);
                    if(t)
                    {
                        len = write_str(buf, sz, (int8 *)t);
                        count += len;
                        buf+=len;
                        sz -= len;
                    } else
                    {
                        len = write_str(buf, sz, "(nil)");
                        count += len;
                        buf+=len;
                        sz -= len;
                    }
                    continue;
                case 'p':
                    t = (void*)va_arg(args, void*);
                    if(t)
                    {
                        len = write_number_UINT( buf, sz, (uint32)t, HEX | UNSIGNED | ADDRESS);
                        count += len;
                        buf+=len;
                        sz -= len;
                    }else
                    {
                        len = write_str(buf, sz, "(null)");
                        count += len;
                        buf+=len;
                        sz -= len;
                    }
                    continue;
                case 'i':
                    flags |= SIZE_32;
                    c = *(format++);
                    break;
                case 'l':
                    flags |= SIZE_64;
                    c = *(format++);
                    break;
            }
            switch(c)
            {
                case 'a':
                    flags |= ADDRESS;
                case 'x':
                    flags |= HEX;
                case 'u':
                    flags |= UNSIGNED;
                case 'd':
                    if((flags & SIZE_32) == SIZE_32)
                    {
                        len = write_number_ULONG( buf, sz, (uint32)va_arg(args, uint32), flags);
                        count += len;
                        buf+=len;
                        sz -= len;
                    }else
                    if((flags & SIZE_64) == SIZE_64)
                    {
                        len = write_number_ULLONG( buf, sz, (unsigned long long)va_arg(args, unsigned long long), flags);
                        count += len;
                        buf+=len;
                        sz -= len;
                    }else
                    {
                        len = write_number_UINT( buf, sz, (uint32)va_arg(args, uint32), flags);
                        count += len;
                        buf+=len;
                        sz -= len;
                    }
                    continue;
                case 'e':
                case 'f':
                    if((flags & SIZE_32) == SIZE_32)
                    {
                        len = write_number_FLOAT( buf, sz, (float)va_arg(args, double), flags);
                        count += len;
                        buf+=len;
                        sz -= len;
                    }else
                    if((flags & SIZE_64) == SIZE_64)
                    {
                        len = write_number_DOUBLE( buf, sz, (long double)va_arg(args, long double), flags);
                        count += len;
                        buf+=len;
                        sz -= len;
                    }else
                    {
                        len = write_number_LDOUBLE( buf, sz, (long double)va_arg(args, long double), flags);
                        count += len;
                        buf+=len;
                        sz -= len;
                    }
                    continue;
            }
        }
        len = write_str( buf, sz, "<format error>");
        count += len;
        buf+=len;
        sz -= len;
        break;
    }

    return count;
}

int32 str_snprintf(char * buf, size_t size, const char *fmt, ...)
{
    va_list args;
    int32 i;

    va_start(args, fmt);
    i=str_vsnprintf(buf,size,fmt,args);
    if( i >= size)
    {
        buf[size-1] = '\0';
    }else
    {
        buf[i] = '\0';
    }
    va_end(args);
    return i;
}

static int32 write_str(char *ptr, int32 sz, char *p)
{
    int32 count = 0;
    //while( *p != '\0' && sz > 0)
    while( (*p != '\0') && (sz > 0) )
    {
        *ptr++ = *p++;
        sz--;
        count++;
    }
    return count;
}

static int32 write_number_ULONG(char *ptr, int32 sz, uint32 number, uint32 flags)
{
    uint32 c;
    int8 buffer[16];
    int8* p = buffer;
    uint32 base = 10;
    int32 count=0;

    if((flags & HEX) == HEX) {
        base = 16;
    } else if(((flags & UNSIGNED) != UNSIGNED) && ((number & 0x80000000ul) > 0)) {
        number = (~number) + 1;
        flags |= MINUS;
    }

    *(p++) = '\0';

    do
    {
        *(p++) = digits[number % base];
    } while((number /= base) > 0);

    if((flags & ADDRESS) == ADDRESS) {
        *(p++) = 'x';
        *(p++) = '0';
    } else if((flags & MINUS) == MINUS) {
        *(p++) = '-';
    }

    while( ((c = *(--p)) != '\0') && (sz > 0) )
    {
        *ptr++ = c;
        sz--;
        count++;
    }
    return count;
}

static int32  write_number_ULLONG(char *ptr, int32 sz, unsigned long long number, uint32 flags)
{
    int32 count =0;
    uint32 c;
    int8 buffer[32];
    int8* p = buffer;
    unsigned long long base = 10ull;

    if((flags & HEX) == HEX)
    {
        base = 16ull;
    }else
    if(((flags & UNSIGNED) != UNSIGNED) && ((number & 0x8000000000000000ull) > 0))
    {
        number = (~number) + 1ull;
        flags |= MINUS;
    }

    *(p++) = '\0';

    do
    {
        *(p++) = digits[number % base];
    } while((number /= base) > 0ull);

    if((flags & ADDRESS) == ADDRESS)
    {
        *(p++) = 'x';
        *(p++) = '0';
    }else
    if((flags & MINUS) == MINUS)
    {
        *(p++) = '-';
    }

    while( ((c = *(--p)) != '\0') && (sz > 0) )
    {
        *ptr++ = c;
        sz--;
        count++;
    }
    return count;
}

static int32 write_number_UINT(char *ptr, int32 sz, uint32 number, uint32 flags)
{
    if(sizeof(uint32) == 4)
    {
        return (write_number_ULONG( ptr, sz, (uint32)number, flags));
    }else
    {
        return (write_number_ULLONG( ptr, sz,(unsigned long long)number, flags));
    }
}

static int32 write_number_FLOAT(char *ptr, int32 sz, float number, uint32 flags)
{
    return (write_str(ptr, sz, "?f"));
}

static int32  write_number_DOUBLE(char *ptr, int32 sz, double number, uint32 flags)
{
    return (write_str(ptr, sz, "?f32"));
}

static int32 write_number_LDOUBLE(char *ptr, int32 sz, long double number, uint32 flags)
{
    return (write_str(ptr, sz, "?f64"));
}


